import {serverUrl, timeout, tokenName} from '@/config';
import message from './message'
import {getRefreshToken, getToken, loadToken} from './token';
import type {Arrayable, GetParams, QueryParams} from "@/soar/types/Types";
import soarQuery from './soarQuery';
import {toUrlParams} from "@/soar/utils/data";

const baseURL = serverUrl;

export interface RequestOptions {
	/**
	 * 路径
	 */
	url?: string;
	
	/**
	 * 请求方式
	 */
	method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
	
	/**
	 * 请求头
	 */
	header?: any;
	
	/**
	 * 请求数据
	 */
	data?: any;
	
	/**
	 * 请求参数
	 */
	params?: any;
	
	/**
	 * formData数据
	 */
	formData?: any;
	
	/**
	 * 超时时间
	 */
	timeout?: number;
	
	/**
	 * 发生错误时提示信息
	 */
	errorMsg?: boolean|string;
	
	/**
	 * 请求成功时提示信息
	 */
	successMsg?: boolean|string;
	
	/**
	 * 响应返回数据类型
	 */
	returnType?: 'default' | 'result' | 'raw';
}

/**
 * 刷新token
 * @param {any} { resolve, reject, err, config } 
 * @return 
 */
// @ts-ignore
function refreshToken({ resolve, reject, err, config }) {
	loadToken().then((token) => {
		config.header = {
			...config.header,
			[tokenName]: token,
		}
		uni.request(config).then((res: any) => {
			if (200 <= res.statusCode && res.statusCode < 300) {
				resolve(res);
			} else {
				reject(err);
			}
		}).catch(reject);
	}).catch(() => {
		reject(err)
	})
}

/**
 * 内部请求函数
 * @param {UniNamespace.RequestOptions} config 
 * @return 
 */ 
function innerRequest(config: UniNamespace.RequestOptions) {
	return new Promise((resolve, reject) => {
		uni.request(config)
		.then((res) => {
			if (200 <= res.statusCode && res.statusCode < 300) {
				resolve(res)
			} else {
				const err = new Error(res?.data?.msg);
				err.response = res;
				// 是否是token失效
				if (res?.data?.code === 'A0001' && getRefreshToken()) {
					refreshToken({ resolve, reject, err, config });
				} else {
					reject(err)
				}
			}
		})
		.catch(err => {
			console.error(err);
			reject(err)
		})
	})
}

/**
 * 发起请求
 * @param {RequestOptions} options 
 * @return 
 */ 
export default function request(options: RequestOptions) {
	
	// 参数处理
	let params = options.params;
	if (options.method === 'GET' || options.method === 'DELETE') {
		params = { ...options.data, ...params }
	}
	params = JSON.parse(JSON.stringify(params || {}))
	if (params.$soarQuery) {
		params = { ...soarQuery(params.$soarQuery), ...params };
		delete params.$soarQuery;
	}
	params = toUrlParams(params);
	// 路径
	let url = baseURL + options.url;
	if (params.length) {
		url = `${url}${url.includes('?') ? '&' : '?'}${params}`
	}
	
	// 数据
	let data = options.data;
	
	// 请求头处理
	let header = { 
		'soar-response-pack-mode': 'pack',
		[tokenName]: getToken(),
		...options.header,
	};
	if (options.method === 'POST' || options.method === 'PUT') {
		header = { ...header, 'Content-Type': 'application/json;' }
	}
	
	// 处理formData数据
	if (options.formData) {
	  data = options.formData;
	  header = { ...header, 'Content-Type': 'application/x-www-form-urlencoded' };
	}
	
	// 请求参数配置
	const config: UniNamespace.RequestOptions = {
		url,
		data,
		header,
		method: options.method,
		timeout: options.timeout ?? timeout
	}
	
	// 发起请求
	return innerRequest(config)
		.then((res: any) => {
			// 请求成功处理
			if (options.successMsg) {
			  message.success(typeof options.successMsg === 'string' ? options.successMsg : res.data.msg || '操作成功');
			}
			// 响应数据
			if (options.returnType === 'raw') {
				return res;
			} else if (options.returnType === 'result') {
				return res.data.data;
			} else {
				return res.data;
			}
		}).catch(err => {
			// 请求失败处理
			const res = err.response;
			// 是否是token失效
			if (options.errorMsg) {
			  message.error(typeof options.errorMsg === 'string' ? options.errorMsg : res.data.msg || '操作失败');
			}
			return err;
		})
}

/**
 * get请求
 */
request.get = (url: string, options?: RequestOptions) => {
	return request({
		...options,
		url,
		method: 'GET',
		errorMsg: true,
	})
}

/**
 * post请求
 */
request.post = (url: string, options?: RequestOptions) => {
	return request({
		...options,
		url,
		method: 'POST',
		errorMsg: true,
		successMsg: true,
	})
}

/**
 * put请求
 */
request.put = (url: string, options?: RequestOptions) => {
	return request({
		...options,
		url,
		method: 'PUT',
		errorMsg: true,
		successMsg: true,
	})
}

/**
 * delete请求
 */
request.delete = (url: string, options?: RequestOptions) => {
	return request({
		...options,
		url,
		method: 'DELETE',
		errorMsg: true,
		successMsg: true,
	})
}

/**
 * 文件上传
 */
request.upload = function (url: string, config: UploadConfig) {
	if (!url.startsWith('http')) {
		url = baseURL + url;
	}
	return new Promise((resolve, reject) => {
		const uploadTask = uni.uploadFile({
			url, //仅为示例，非真实的接口地址
			filePath: config.filePath,
			name: config.name,
			formData: config.formData,
			header: { ...config.header, [tokenName]: getToken() },
			success: (res) => {
				const data: any = JSON.parse(res.data);
				if(data.success) {
					resolve(data)
				} else {
					reject(data.message)
				}
			},
			fail(err) {
				message.error('操作失败')
				reject(err)
			}
		});

		// @ts-ignore
		uploadTask.onProgressUpdate(config.onProgressUpdate);
	})
}

interface UploadConfig {
	/**
	 * 上传的文件本地路径，tempFilePath
	 */
	filePath: string;
	/**
	 * 服务器接收文件流的字段名称
	 */
	name: string,
	/**
	 * 请求携带的其他参数
	 */
	formData?: any;
	/**
	 * 请求头
	 */
	header?: any;

	/**
	 * 监听上传进度
	 * @param res
	 */
	onProgressUpdate?(res: any): void;
}
interface Api<T> {
	/**
	 * 接口路径
	 */
	urls: {
		create: string;
		update: string;
		remove: string;
		detail: string;
		query: string;
		exist: string;
		exports: string;
		imports: string;
	},
	/**
	 * 创建
	 * @param data 数据
	 * @param config 配置
	 */
	create(data: T, config?: any): Promise<any>;
	/**
	 * 修改
	 * @param data 数据
	 * @param config 配置
	 */
	update(data: T, config?: any): Promise<any>;
	/**
	 * 删除
	 * @param ids id 或 id数组
	 * @param config 配置
	 */
	remove(ids: Arrayable<string | number>, config?: any): Promise<any>;
	/**
	 * 查询详情
	 * @param id id
	 * @param config 配置
	 */
	detail(id: string | number, config?: any): Promise<T>;
	/**
	 * 分页查询
	 * @param params 参数
	 * @param config 配置
	 */
	query(params?: QueryParams, config?: any): Promise<any>;
	/**
	 * 查询列表
	 * @param params 参数
	 * @param config 配置
	 */
	list(params?: QueryParams, config?: any): Promise<any>;
	/**
	 * 根据id查询
	 */
	listByIds(ids: Arrayable<string | number>, fields: Arrayable<string | keyof T>): Promise<any>;
	/**
	 * 统计总数
	 * @param params 参数
	 * @param config 配置
	 */
	count(params?: QueryParams, config?: any): Promise<any>;
	/**
	 * 查询是否存在
	 * @param params 参数
	 * @param config 配置
	 */
	exist(params?: QueryParams, config?: any): Promise<any>;
	/**
	 * 保存或更新
	 * @param data
	 * @param isUpdate
	 */
	saveOrUpdate(data: T, isUpdate?: boolean): Promise<any>;
	/**
	 * 创建校验
	 */
	createValidator(getParams: GetParams, message?: string): any;
}

export function createApi<T>(basePath: string, subject = ''): Api<T> {
	const apiObj: Api<T> = {
		/**
		 * 接口路径
		 */
		urls: {
			create: `${basePath}/create`,
			update: `${basePath}/update`,
			remove: `${basePath}/remove`,
			detail: `${basePath}/detail`,
			query: `${basePath}/query`,
			exist: `${basePath}/exist`,
			exports: `${basePath}/export`,
			imports: `${basePath}/import`,
		},
		/**
		 * 创建
		 * @param data 数据
		 * @param config 配置
		 */
		create(data: T, config?: any): Promise<any> {
			if (!data) {
				message.error('缺少数据');
				return Promise.reject();
			}
			return request.post(apiObj.urls.create, {
				title: `新增${subject}`,
				...config,
				data,
			});
		},
		/**
		 * 修改
		 * @param data 数据
		 * @param config 配置
		 */
		update(data: T, config?: any): Promise<any> {
			if (!data) {
				message.error('缺少数据');
				return Promise.reject();
			}
			return request.post(apiObj.urls.update, {
				title: `修改${subject}`,
				...config,
				data,
			});
		},
		/**
		 * 删除
		 * @param ids id 或 id数组
		 * @param config 配置
		 */
		remove(ids: Arrayable<string | number>, config?: any): Promise<any> {
			// @ts-ignore
			if (!ids || ids.length === 0) {
				message.error('请选择要删除的数据');
				return Promise.reject();
			}
			return request.post(apiObj.urls.remove, {
				title: `删除${subject}`,
				...config,
				data: ids,
			});
		},
		/**
		 * 查询详情
		 * @param id id
		 * @param config 配置
		 */
		detail(id: string | number, config?: any): Promise<T> {
			return request.get(apiObj.urls.detail, {
				title: `获取${subject}`,
				params: { id },
				...config,
			});
		},
		/**
		 * 分页查询
		 * @param params 参数
		 * @param config 配置
		 */
		query(params?: QueryParams, config?: any): Promise<any> {
			return request.get(apiObj.urls.query, {
				title: `查询${subject}`,
				...config,
				params,
			});
		},
		/**
		 * 查询列表
		 * @param params 参数
		 * @param config 配置
		 */
		list(params?: QueryParams, config?: any): Promise<any> {
			params ??= {};
			params.pageSize ??= 99999;
			params.queryType = 'list';
			return apiObj.query(params, config).then((res) => {
				return Array.isArray(res) ? res : res.list;
			});
		},
		/**
		 * 根据id查询
		 */
		listByIds(ids: Arrayable<string | number>, fields: Arrayable<string>) {
			return apiObj.list({ $soarQuery: { 'id-in': ids, fields } });
		},
		/**
		 * 统计总数
		 * @param params 参数
		 * @param config 配置
		 */
		count(params?: QueryParams, config?: any): Promise<any> {
			params ??= {};
			params.queryType = 'total';
			return apiObj.query(params, config).then((res) => {
				return typeof res === 'number' ? res : res.total;
			});
		},
		/**
		 * 查询是否存在
		 * @param params 参数
		 * @param config 配置
		 */
		exist(params?: QueryParams, config?: any): Promise<any> {
			// @ts-ignore
			return request.get(apiObj.urls.exists, {
				title: `查询${subject}是否已存在`,
				...config,
				params,
			});
		},
		/**
		 * 保存或更新
		 * @param data
		 * @param isUpdate
		 */
		saveOrUpdate(data: T, isUpdate = false): Promise<any> {
			if (isUpdate) {
				return apiObj.update(data);
			}
			return apiObj.create(data);
		},
		/**
		 * 创建校验
		 */
		createValidator(getParams: GetParams, message = '数据已存在') {
			return {
				validator: (val: any) => {
					const params = getParams(val);
					if (params) {
						params.queryType = 'total';
						return this.query(params).then((res: { total: number }) => {
							return res.total === 0;
						});
					}
					return true;
				},
				message,
			};
		},
	};
	return apiObj;
}